#include "polynomial.h"
#include <algorithm>
#include <stdarg.h>

namespace SPLINTER
{
    Polynomial::Polynomial() : coefficients(0)
    {
    }

    Polynomial::Polynomial(int degree, ...) : coefficients(degree)
    {
        va_list args;
        va_start(args, degree);
        for (int i = 0; i < degree; i++)
        {
            auto arg = va_arg(args, double);
            coefficients(i) = arg;
        }
        va_end(args);
    }

    Polynomial::Polynomial(const DenseVector &coefficients) : coefficients(coefficients) {}

    Polynomial::~Polynomial()
    {
    }

    size_t Polynomial::degree() const
    {
        return coefficients.size() - 1;
    }

    const DenseVector &Polynomial::getCoefficients()
    {
        return coefficients;
    }

    double Polynomial::operator()(double x) const
    {
        double ret = 0;
        for (int i = 0; i <= degree(); i++)
        {
            ret = ret * x + coefficients[i];
        }
        return ret;
    }

    Polynomial Polynomial::operator+(const Polynomial &that)
    {
        bool is_big = coefficients.size() > that.coefficients.size();
        const Polynomial *bigger = is_big ? this : &that,
                         *smaller = is_big ? &that : this;

        DenseVector ret(bigger->coefficients);
        int i_smaller = smaller->degree(), i = ret.size() - 1;
        while ((i >= 0) && (i_smaller >= 0))
        {
            ret(i--) += smaller->coefficients(i_smaller--);
        }
        return Polynomial(ret);
    }

    Polynomial Polynomial::operator-(const Polynomial &that)
    {
        DenseVector ret(std::max(coefficients.size(), that.coefficients.size()));
        int i_this = degree(), i_that = that.degree(), i_ret = ret.size() - 1;
        while ((i_this >= 0) || (i_that >= 0))
        {
            double this_val = 0, that_val = 0;
            if (i_this >= 0)
            {
                this_val = coefficients(i_this--);
            }
            if (i_that >= 0)
            {
                that_val = that.coefficients(i_that--);
            }
            ret(i_ret--) = this_val - that_val;
        }
        return Polynomial(ret);
    }

    Polynomial Polynomial::operator*(const Polynomial &that)
    {
        DenseVector ret(degree() + that.degree() + 1);
        for (int i = 0; i < ret.size(); i++)
            ret(i) = 0;
        for (int i = 0; i < ret.size(); i++)
        {
            for (int j = 0; j < coefficients.size(); j++)
            {
                int k = i - j;
                if (k >= 0 && k <= that.degree())
                    ret(i) += coefficients(j) * that.coefficients(k);
            }
        }
        return Polynomial(ret);
    }

    Polynomial Polynomial::operator*(double alpha)
    {
        DenseVector ret(coefficients);
        for (int i = 0; i < ret.size(); i++)
        {
            ret(0) *= alpha;
        }
        return Polynomial(ret);
    }

    Polynomial Polynomial::derivation()
    {
        if (degree() <= 0)
        {
            return Polynomial(1, 0.0);
        }
        DenseVector ret(degree());
        for (int i = 0; i < ret.size(); i++)
        {
            ret(i) = coefficients(i) * (degree() - i);
        }
        return Polynomial(ret);
    }

    std::string Polynomial::to_string()
    {
        int d = degree();
        if (d < 0)
            return "";
        std::string ret = "";
        for (int i = 0; i < coefficients.size() - 1; i++)
        {
            ret = ret + std::to_string(coefficients[i]) + "*x^" + std::to_string(d--) + "+";
        }
        return ret + std::to_string((coefficients[degree()]));
    }

    std::string Polynomial::to_string() const
    {
        int d = degree();
        if (d < 0)
            return "";
        std::string ret = "";
        for (int i = 0; i < coefficients.size() - 1; i++)
        {
            ret = ret + std::to_string(coefficients[i]) + "*x^" + std::to_string(d--) + "+";
        }
        return ret + std::to_string((coefficients[degree()]));
    }
}